package emulator.cbm.basic;

import java.util.LinkedList;
import java.util.Queue;

import emulator.hardware.memory.MemoryBlockInterface;
import emulator.util.ByteHelper;

public class BasicHelper {
	
	static final int BASIC_START_ADDRESS = 0x2B;
	static final int BASIC_END_ADDRESS = 0x2D;
	static final int KEY_BUFFER_IDX_ADDRESS = 0xC6;
	static final int KEY_BUFFER_START_ADDRESS = 0x277;
	static final int KEY_BUFFER_END_ADDRESS = 0x280;
	
	private static MemoryBlockInterface memory_block;
	
	public static void setMemoryBlock(MemoryBlockInterface memory_block) {
		BasicHelper.memory_block = memory_block;
	}

	private static byte[] getMemory() {
		return memory_block.getData();
	}

	private static BasicHelper instance = new BasicHelper();

	private static BasicHelper getInstance() {
		return instance;
	}

	public static void relinkProgram()
	{
		byte[] memory = getMemory();
		int i_ptr = getBasicStart();
		if (i_ptr > 0)
		{
			memory[i_ptr-1] = 0;
			while (memory[i_ptr+1] != 0)
			{
				int i;
				for (i = 5; memory[i_ptr+i] != 0 && i < 128; i++)
					;
				if (i > 127)
				{
					write_word(memory, i_ptr, 0);
					break;
				}
				i++;
				write_word(memory, i_ptr, i_ptr+i);
				i_ptr += i;
			}
			write_word(memory, BASIC_END_ADDRESS, i_ptr+2);
		}
	}

	public static void enterText(String text)
	{
		if (text.length() > 0)
		{
			byte[] keys = new byte [text.length()];
			for (int i = 0; i < text.length(); i++) {
				keys[i] = translateChar((byte)text.charAt(i));
			}
			getInstance().getKeyStufferInstance().putKeys(keys);
		}
	}
	
	public static void clearText()
	{
		getInstance().getKeyStufferInstance().clearBuffer();
	}

	private static byte translateChar(byte ch) {
		return (ch == 0x0a ? 0x0d : ch);
	}

	private static void write_word(byte[] memory, int address, int word) {
		memory[address]= (byte)(word & 0xff);
		memory[address+1] = (byte)((word >> 8) & 0xff);
	}
		
	private static int read_word(byte[] memory, int address) {
		return ByteHelper.byteToInt(memory[address]) + (ByteHelper.byteToInt(memory[address+1]) << 8);
	}
	
	KeyStuffer getKeyStufferInstance()
	{
		if (stuffer_instance == null)
		{
			stuffer_instance = new KeyStuffer();
			stuffer_instance.start();
		}
		return stuffer_instance;
	}
	
	private KeyStuffer stuffer_instance = null;
	
	private class KeyStuffer extends Thread {
		private Queue<Byte> key_buffer = new LinkedList<Byte>();
		
		public synchronized void putKeys(byte[] keys)
		{
			for (byte key : keys)
				key_buffer.add(key);
			notify();
		}
		
		public synchronized void clearBuffer()
		{
			key_buffer.clear();
			notify();
		}

		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public synchronized void run()
		{
			for (;;)
			{
				try {
					while (key_buffer.isEmpty())
						wait();
					while (!key_buffer.isEmpty())
					{
						byte[] memory = memory_block.getData();
						while (memory[KEY_BUFFER_IDX_ADDRESS] > 0 &&
							   !key_buffer.isEmpty())
							wait(50);
						if (!key_buffer.isEmpty())
							stuffKeys();
					}
				}
				catch (InterruptedException e)
				{}
			}
		}

		private void stuffKeys()
		{
			byte[] memory = getMemory();
			int buffer_size = KEY_BUFFER_END_ADDRESS - KEY_BUFFER_START_ADDRESS + 1;
			byte key_buffer_idx = memory[KEY_BUFFER_IDX_ADDRESS];
			for (;
				 key_buffer_idx < buffer_size && !key_buffer.isEmpty();
			     key_buffer_idx++)
			{
				memory[KEY_BUFFER_START_ADDRESS+key_buffer_idx]
				       = key_buffer.remove();
			}
			memory[KEY_BUFFER_IDX_ADDRESS] = key_buffer_idx;
		}
	}

	public static int getBasicStart() {
		return read_word(getMemory(), BASIC_START_ADDRESS);
	};
	
	public static int getBasicEnd() {
		return read_word(getMemory(), BASIC_END_ADDRESS);
	};
}
